Skip to content

fix(metadata-protocol): a customization pair's row of a name two namespaces hold is judged against its own base, whichever package registered first (ADR-0150 D4) - #33129

Merged
objectstack-fleet[bot] merged 94 commits into
mainfrom
claude/project-thread-h61law
Nov 13, 2026
Merged

objectstack-fleet[bot] merged 94 commits into
mainfrom
claude/project-thread-h61law

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #32552

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:两个不同命名空间的包都出货同名条目(例如 flow f),各自带一个声明过的 .custom 覆盖时,删除覆盖、回滚提交、启动普查这三处原来只拿“先注册的那个包”当基准判断;现在对每个出货包分别判断,后注册那个包的覆盖也能正常删除、回滚。启动普查与读取保持一致:flow 和 action 不再把第二个包正在生效的覆盖报成“无人服务、请删除”;页面、应用、数据集、文档、手册、映射这六类读取仍只认先注册的包(metadata-protocol: a second holder's declared .custom override is still judged against the first shipper only, for the sealed served-override types and the overlay list filter (ADR-0150 D4 follow-ups of #32613) #33131 再改),普查也照旧。
  • 为什么改:结果原本取决于包的注册顺序(ADR-0150 D4 要求每个持有包的声明覆盖各自成立);后注册的包删自己的覆盖会被 403 NOT_OVERRIDABLE 拒掉,回滚失败,普查还会建议删掉一个正在生效的定制。
  • 风险与回滚:只放宽(widening),不收紧任何已有答案;单一持有包的行为逐条不变(CONTROL 钉住)。不是任何持有包的配对行仍然 403。回滚 = revert 本 PR,只动 metadata-protocol 三个源文件。本轮(复审 s1)把普查收窄到与 metadata-protocol: getMetaItemsForExecution files one pair package's bound flow override under another package's slot, by registration order (ADR-0150 D4, red line 2) #32613 的读取一致。
  • 先看:packages/metadata-protocol/src/protocol-environment-rows.ts 新增的 someShipperOf(判定核心);protocol-diff-delete-load.ts 的删除判定与启动普查两处 hunk;protocol-class-head.ts 的 restoreWriteFor。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/protocol-flow-custom-override.test.ts,预期 45/45 通过;其中 [#32552] 组两种注册顺序、各禁用一个持有包、映射类 CONTROL 以及单持有包 CONTROL 全绿。
  • 需要你做的:无,席位落地。

What changes

For a name two packages of two namespaces ship, each with its paired .custom holding a declared override (ADR-0146 D2), three answers judged the pair's row against the one package the bare-name artifact lookup (packagedArtifactOwner) answers, so they depended on which package registered first. Each now asks someShipperOf (new, protocol-environment-rows.ts): the bare-name owner first and alone while it holds (a one-holder name answers exactly as before), then every package that ships the name (shippingPackageIds over shippedArtifactsOf). No shipper is picked: the row judged is always the one its own package_id names.

Door Second-registered holder B, before Now One holder (CONTROL)
DELETE /meta/TYPE/NAME?package=B.custom (deleteMetaItem); pinned for a flow, and an action in the condition-1 probe 403 NOT_OVERRIDABLE; B's override stays 200, B's override removed (runtime-only), A's untouched; every listed and execution-view entry of the name is its own package's unchanged: 200, removed
revertCommit of a commit into B.custom (restore limb; the per-item rollback shares restoreWriteFor); pinned for a flow item in failed[], NOT_OVERRIDABLE; nothing restored restored as the pair's declared override unchanged: restored
boot census metadata_sealed_overlay_unserved (loadMetaFromDb), a flow or an action only named B's live declared override as unserved, to delete not named unchanged: not named

Every row is the widening arm (epic ruling 6819293900 condition 2). With one of the two holders disabled (ADR-0150 D3) the bare-name answer is still the first-registered holder, so the same three answers moved the same way whichever holder is disabled; a binding never moves with a reversible disable. Unchanged in both: a row of a package that is no shipper's pair keeps its removal 403 and is still named by the census.

The census judges a row as the reads serve it (declinesStoredRow). Since #32613 (#33126) the reads serve a second holder's declared override per shipper for a flow and an action only, so the census moves for those two types alone. A page, app, dataset, doc, book or mapping keeps the name's first-registered shipper in the census, as in the reads, until #33131 moves them: the second holder's override of such a name is still named (review 6859128505 s1).

Files: protocol-environment-rows.ts (+someShipperOf), protocol-diff-delete-load.ts (removal judgement; census, per shipper for a flow and an action), protocol-class-head.ts (restoreWriteFor), the changeset, and the pins in packages/objectql/src/protocol-flow-custom-override.test.ts.

Tests

packages/objectql/src/protocol-flow-custom-override.test.ts, describe [#32552], each in both registration orders:

  • ?package=B.custom removes B's override, never A's (and the mirror order).
  • CONTROL: removing a row of a package that is neither holder's pair keeps its refusal.
  • each holder's commit into its own pair reverts.
  • the boot census names no holder's declared override as unserved, and still names a stranger's row.
  • epic ruling 6819293900 condition 1 probe: after the second holder's override is removed, every listed flow and action is its own package's, beside a package-less row of the name.
  • with each holder disabled in turn: each holder's pair removal and commit revert are admitted, and the census names neither.
  • CONTROL (new this round): two holders' declared mapping overrides; the census still names the second-registered holder's, mapping×1, in both orders.
  • CONTROL, one holder disabled, and CONTROL, one holder: removal, revert and census answer as before.

Ablation, two legs on head d311b66322, each rebuilt with ablation-dist-preflight proving the mutation reached dist/, restored by git checkout HEAD with HEAD blob hashes matched and a clean whole-tree git status:

  • Leg A, the census type restriction removed (perShipperType = true, marker present in 2 built files): 2 failed / 43 passed of 45, exactly the two mapping CONTROLs: AssertionError: expected [] to have a length of 1 but got +0.
  • Leg B, the fix's three source files put back to origin/main (someShipperOf absent from all 24 built files): 12 failed / 33 passed of 45, every two-holder pin red, every CONTROL green; quoted Serialized Error: { code: 'NOT_OVERRIDABLE', status: 403 }.
  • Restore: rebuilt, someShipperOf present and the leg-A marker absent in dist/, 45/45.

Acceptance notes

Verification

All local readings on head d311b66322.

Affected suites and typecheck
  • pnpm --filter @objectstack/metadata-protocol test: Test Files 337 passed, 7 skipped (344); Tests 22803 passed, 36 skipped. typecheck exit 0. Lock VERDICT command-exit 0.
  • pnpm --filter @objectstack/objectql test: Test Files 595 passed (595); Tests 12851 passed (12851). typecheck exit 0. Lock VERDICT command-exit 0.
  • Focused file: 45/45.
dispatch-gates

node scripts/pm/dispatch-gates.mjs --commands derived 69 commands from 5 paths vs merge base bf02d33bd, all 69 exit 0 (check:dual-build-cjs-loads measured this round: 123 require entry points across 72 packages load). dispatch-gates --ran: "69 derived famil(ies) accounted for — 69 run, 0 NOT-MEASURED (a DERIVED zero — all 69 recorded an exit code and none of them is 3)".

node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-engine-split-ratio.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:error-code-provenance :: exit 0
pnpm check:adr-symbol-anchors :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:init-service-contract :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:object-def-param-keys :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:optional-error-sink :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:settings-bind-window :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:startup-registry-verdict :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vouch-for-reference-callers :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
Lint and dogfood
  • eslint --no-inline-config on the two TS files changed this round: exit 0. Round 1 pnpm lint on c35a911b30: exit 0.
  • Full dogfood, round 1 on c35a911b30 (not owed: no !, no (narrowing) arm): Test Files 460 passed, 3 skipped (463); Tests 3874 passed, 42 skipped (3916); lock VERDICT command-exit 0. Not re-run for the census-only delta.

Generated by Claude Code

…, not per deployment (ADR-0150 child B, part 1 of the webhook family)

WIP: the switch, the platform-requested auth.* names, the bundle-addition
rule, the package-scoped /meta read and the send's holder-filtered bundle.

Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v
Co-authored-by: Claude <noreply@anthropic.com>
…latform-requested auth names and the bundle-addition rule

Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v
Co-authored-by: Claude <noreply@anthropic.com>
…nd a pair's member as the package's own

A read naming a dependent whose only member of a bundled name is an addition
(a locale its base does not ship) was refused not-held-by-qualifier once
email_template joined the package-scoped readers, and a pair's added member
on its base's address was replaced by the registry's item. The qualifier now
asks the holders as the caller it names, and keeps an item of a pair of the
admitted package.

The co-owner dogfood case asserts the ADR-0146 D2 refusal a co-owner meets
first at install-local.

Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v
Co-authored-by: Claude <noreply@anthropic.com>
…he email template's narrowing; changeset

Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v
Co-authored-by: Claude <noreply@anthropic.com>
…s base's address for a bundled type only

Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v
Co-authored-by: Claude <noreply@anthropic.com>
…rowing; pin the qualifier's holder ask for the nine

Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v
Co-authored-by: Claude <noreply@anthropic.com>
…mplate send resolves its name from the caller's package (ADR-0150 D3)

The flow notify node records the package whose own flow body is armed
beside its template reference; the email and inbox channels hand it to
sendTemplate/renderTemplate as callerPackageId; the registry loader
resolves the name from it and, for a name two packages hold, reads the
resolved holder's own address. A send that names no package keeps the
bare-name meaning.

Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS
Co-authored-by: Claude <noreply@anthropic.com>
…is refused on both exact reads and listed by the upgrade report

Two packages of one App share the App's customization pair, so a locale
row there with no overrides declaration cannot say whose template it
extends: each holder's exact read refuses it naming every holder, the
list scoped to a holder leaves it out, and os migrate stored-references
lists it under pairMembers. One holder, the App's own address and a row
that declares its base keep being served. Pins for F1 (loader rungs,
service ladder, notify node, channels, dogfood) and F2; changeset.

Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS
Co-authored-by: Claude <noreply@anthropic.com>
…otype.at, which the package's lib target does not declare

Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS
Co-authored-by: Claude <noreply@anthropic.com>
…) for email_template, beside api

Two packages of two namespaces may now each hold one email template name, so
the second package's item no longer overwrites the first's in MetadataManager's
in-memory store: a read naming a package answers that package's own, and a read
naming none of a two-holder name is the D3 refusal (#31527 B-shape item 1).
Pins that email_template has no metadata redactor, so a save never takes the
carry-forward's code-layer read for it (item 4).

Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS
Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf

# Conflicts:
#	packages/objectql/src/package-scoped-e8-readers.test.ts
#	packages/objectql/src/package-scoped-read.ts
…-3giljf

# Conflicts:
#	packages/cli/src/utils/stored-package-references.ts
#	packages/metadata/src/package-slotted-store.ts
…es hold is no holder's own

A package-less row (the environment's item, or a sys_metadata row bound to
no package) of a name two or more packages hold is the customization of one
holder and cannot say which (ADR-0150 D4). Each holder's exact read now
refuses it 409 ambiguous-in-closure naming every holder, the list scoped to a
holder (which a send made from it reads) leaves it out, and
os migrate stored-references lists it under packageLessRows. One holder,
the _env address and the platform-requested auth mails are unchanged. The
rule sits in the exact qualifier, so it covers every type read by package
(epic ruling 6761802819 on #31699).

Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS
Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf

# Conflicts:
#	packages/objectql/src/package-scoped-e8-readers.test.ts
#	packages/objectql/src/package-scoped-read.ts
…eview round on #32214

- notify node: a literal payload `template` string records the armed package
  too, as the channels read it (F-A).
- the pair-row owner check is one self-contained function,
  pairRowOnAddress, which the exact read and the scoped list both ask: the
  declared base first (F-B), the App's own pair, two owners refused, a sole
  owner that is not one of the address's bases not served (S2).
- SchemaRegistry.getPackageScopedItem skips a pair row that declares
  another holder as its base, so the App's address answers its own item.
- stored-references hints, its module doc and the changeset say truthfully
  that no door binds an added member to one base today (#28777) (F-C).
- changeset: Metadata-protocol narrowed; slotted set lists api, agent, skill
  and email_template; the package-less rule names PACKAGE_SCOPED_READ_TYPES;
  the pair row's sole-owner and App-slot rules.
- email-service docs page: the new callerPackageId member.

Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS
Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf

# Conflicts:
#	packages/cli/src/commands/migrate/stored-references.integration.test.ts
#	packages/metadata/src/package-slotted-store.ts
#	packages/objectql/src/bare-name-one-holder.ts
#	packages/objectql/src/registry-bare-name-one-holder-ai-family.test.ts
#	packages/objectql/src/registry.ts
#	packages/qa/dogfood/test/bare-name-one-holder-flow-family.dogfood.test.ts
#	packages/services/service-automation/src/engine.ts
…-3giljf

# Conflicts:
#	packages/metadata-protocol/src/protocol-exact-qualifier.ts
#	packages/objectql/src/bare-name-one-holder.ts
…ead an email template by package, as SchemaRegistry does

With email_template narrowed to the namespace, #32277 refuses a bare read
of it on a registry that says it reads the type through getItem
(`narrowed`, 501). SchemaRegistry reads it by package since this change,
so the four doubles that bare-read an email template say so and resolve
it through their own item.

Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS
Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf

# Conflicts:
#	packages/metadata-protocol/src/index.ts
#	packages/metadata-protocol/src/protocol-exact-qualifier.ts
#	packages/objectql/src/bare-name-one-holder.ts
#	packages/objectql/src/package-scoped-exact-qualifier.test.ts
…ed text

The doc authoring guard refuses an internal issue id in a runtime string;
the reference moves to an adjacent comment.

Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS
Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf

# Conflicts:
#	packages/metadata/src/package-slotted-store.ts
#	packages/objectql/src/package-scoped-e8-readers.test.ts
#	packages/objectql/src/package-scoped-exact-qualifier.test.ts
#	packages/objectql/src/package-scoped-read.ts
…age reaches the package-less owner check

`''` and the provenance sentinel `sys_metadata` count as no package before
the owner check, so a package-less row the registry answers for a name two
packages hold is refused at both exact addresses (contract review R2-A).
Pins the exact read and the list of a member the holder itself ships beside
such a row (R2-B); the changeset states the mail consequence and the
review's prose nits.

Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS
Co-authored-by: Claude <noreply@anthropic.com>
…t, and the exact read of a member the holder ships refuses beside it (R3-A)

The fixture wrote the canonical member's package-less row as
bundle_member 'en-US'; the store spells the canonical member NULL
(overlayMemberValue), so the read never found it and the pin asserted
the registry's answer. Written as stored, ?package=A for a member A
ships answers 409 ambiguous-in-closure naming both holders, while A's
list and the send's read serve A's own (epic ruling 6792134290).

Claude-Session: https://claude.ai/code/session_01QSeKYm8sWjp2Pk76warN7w
Co-authored-by: Claude <noreply@anthropic.com>
…er of every slot (R3-B)

With email_template in PACKAGE_SLOTTED_STORE_TYPES, DatabaseLoader's
loadMany / loadManyKeyed collapsed to one row per name, dropping every
other stored locale of a bundle and every other package's bundle, where
main answered every row. A bundled type now keeps one row per
(package, name, member), the canonical member spelled null as the store's
bundle_member column spells it; MemoryLoader answers every slot of a
bundled type alike. A rollback by name of a one-slot name restores the
row of the member its body declares (epic ruling 6796068023).

Claude-Session: https://claude.ai/code/session_01QSeKYm8sWjp2Pk76warN7w
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface label Nov 13, 2026
@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol, touching 45 documentable anchor(s).

13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/actions-as-tools.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/ai/index.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/api/client-sdk.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/api/environment-routing.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/api/wire-format.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/concepts/architecture-overview.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/concepts/metadata-lifecycle.mdx (via getOverlayRepo (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/deployment/cloud-connection.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/deployment/publish-and-preview.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/deployment/single-project-mode.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/protocol/kernel/http-protocol.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/ui/actions.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/ui/forms.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))
  • content/docs/releases/v17/17-7.mdx (via environmentId (literal, a string literal in deleteMetaItemUnaudited))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 65 of 247 client-bound route-ledger rows — the other 182 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 182: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 123 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json bf02d33bd64f496aa8166e1a235ef10977927947 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 7af31ee30bd0a5bd032a800a43489dacacabc0cb — the merge of head d311b66322543204efbd2f4526559ffcae4bebb7 into base bf02d33bd64f496aa8166e1a235ef10977927947, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7af31ee30bd0a5bd032a800a43489dacacabc0cb && git checkout 7af31ee30bd0a5bd032a800a43489dacacabc0cb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bf02d33bd64f496aa8166e1a235ef10977927947 d311b66322543204efbd2f4526559ffcae4bebb7 && git checkout -B drift-repro bf02d33bd64f496aa8166e1a235ef10977927947 && git merge --no-ff d311b66322543204efbd2f4526559ffcae4bebb7

node scripts/docs-audit/affected-docs.mjs --json bf02d33bd64f496aa8166e1a235ef10977927947

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs bf02d33bd64f496aa8166e1a235ef10977927947 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv

Contract-review-claim: session_01L9qibzosF5fZk98UHos9cv · head c35a911 · 2026-11-13T16:14Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv

Contract review

Served-tier: TIER_DEFAULT
Head-sha: c35a911b3073eafa6d152e2101a566322cd5c4a0
Local-runs: none

First round on the declaration face (Clause-②: yes (widening); no published schema, no governed path, no narrowing arm), read at 2026-11-13T16:42Z. Inputs: card #32552, the PR body, the diff against merge base 2c98485d (five files), the check-runs on this head, the knock 6858183179. Claim 6858479168. Read for the judgments below, not reviewed as a design: someShipperOf and its three call sites, and the [#32552] pins.

① Derived judgments

  • Clause-②: yes (widening) is right. Two doors admit a request they refused: the removal naming the second holder's pair goes from 403 NOT_OVERRIDABLE to 200, and a commit revert into that pair goes from failed[] to restored. Nothing that was admitted is refused.
  • Metadata-protocol: none is right: nothing an author can write is added, refused or given a new meaning.
  • The one-holder answer does not move. someShipperOf asks the bare-name owner first, and with one shipper the set it walks next holds that same package.
  • No shipper is picked. Each call judges the row or the pair the request itself names, and asks only whether it is a pair of some package that ships the name. A pair of no shipper keeps its 403 and stays in the census (pinned in both orders).
  • The census row is true on this base for a flow only (s1).

② Semver level

  • patch on @objectstack/metadata-protocol follows from none. A widening carries no BREAKING banner and no !, and none is written. No ADR-0087 marker is owed.

③ Boundary flags

Findings:

No must-fix.

Implemented-by: session_016WV4cyN75XjF6J8fLtD21r
Reviewed-by: session_01L9qibzosF5fZk98UHos9cv

VERDICT: PASS

…-h61law

Brings in #33126 (#32613), which serves two holders' declared overrides
per shipper for a flow and an action only; the census follows it next.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyN75XjF6J8fLtD21r
…ride per shipper for a flow and an action only, as the reads serve it

The six sealed types keep the name's first-registered shipper in the census,
as in the reads, until #33131; a mapping CONTROL pins it in both
registration orders. The changeset table names what each row is pinned on.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyN75XjF6J8fLtD21r
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv

Contract-review-claim: session_01L9qibzosF5fZk98UHos9cv · head d311b66 · 2026-11-13T19:15Z

Delta of PASS 6859128505 on the moved head.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d311b66322543204efbd2f4526559ffcae4bebb7
Local-runs: none
Scope: packages/metadata-protocol/src/protocol-diff-delete-load.ts, packages/objectql/src/protocol-flow-custom-override.test.ts, packages/metadata-protocol/src/protocol-environment-rows.ts, packages/metadata-protocol/src/protocol-class-head.ts, .changeset/32552-pair-row-removal-every-shipper.md (its declaration lines and level) + read as cited code, not reviewed: the census block of packages/metadata-protocol/src/protocol-diff-delete-load.ts on main, declinesStoredRow in packages/metadata-protocol/src/protocol-lock-state.ts as #33126 landed it.

Delta review of PASS 6859128505, read at 2026-11-13T19:42Z. Inputs: the interdiff c35a911b..d311b663 (one merge of main with no conflict, and one commit of this branch on three files), the PR title and body as they stand, the check-runs on this head, the knock 6862521648. Claim 6862544400. protocol-environment-rows.ts and protocol-class-head.ts are unchanged since c35a911b, and what the earlier record judged in them stands as read there.

① Derived judgments

  • Clause-②: yes (widening) and Metadata-protocol: none stand. The commit takes a widening back for six types and adds none: the census moves for a flow and an action only.
  • The census now asks the question the reads ask. For a flow and an action it judges the row against every shipper, as declinesStoredRow does since fix(metadata-protocol)!: two packages' declared overrides of one flow or action name each serve in their own package's slot, in any registration order (ADR-0150 D4) #33126. For any other type it is the expression main has, the name's first-registered shipper alone, so nothing moves there.
  • The removal and the revert still judge every shipper for every type. That is consistent with the census: for a sealed type the second holder's override is a row the reads decline and the census names for deletion, and the removal that deletes it is now admitted where it was refused.

② Semver level

  • patch on @objectstack/metadata-protocol, no BREAKING banner, no !, no ADR-0087 marker: unchanged, and right for a widening of kind none.

③ Boundary flags

④ Design judgment

packages/metadata-protocol/src/protocol-diff-delete-load.ts (s1). Fixed. perShipperType is flow or action by the canonical type; the two arms share one predicate call, so only the base it is judged against differs. The comment says why and names the card that moves the six. The new CONTROL boots two holders that each ship a mapping, stores each holder's declared mapping override, and expects the census to name exactly one row in both registration orders. By the report's ablation it goes red when the type restriction is removed, so it holds the line s1 asked for.

.changeset/32552-pair-row-removal-every-shipper.md and the PR table (s1, n1). The census row reads "a flow or an action only", a paragraph says what the six sealed types keep and until when, and each row names what it is pinned on.

Findings:

  • None on this delta.

No must-fix. s1 and n1 of record 6859128505 are closed on this head. The census line carries a type and a name and no package, so mapping×1 over two stored rows is as far as the CONTROL can pin which row is named.

Implemented-by: session_016WV4cyN75XjF6J8fLtD21r
Reviewed-by: session_01L9qibzosF5fZk98UHos9cv

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

epic:#31527#2 · os-elon-musk · session_016WV4czFhtRVZNBeHdQA6PW

ACCEPT · 2026-11-13T19:43Z · head d311b66322543204efbd2f4526559ffcae4bebb7

Verdict: ACCEPT: Fixes #32552. Not governed, 5 files, +400 / −16 (check-governed-merges --pr 33129). This seat (the sole epic seat of #31527) lands it, per the maintainer's order to carry the epic's already-branched p2 cards to merge and the standing order that seats review and land with pm-dispatch.

席位核对

  1. Shape: draft PR to main, Fixes #32552, Clause-②: yes (widening) / Metadata-protocol: none per ruling 6819293900 cond. 2; files inside the re-posted claim 6855957599 (which corrects the seat's first claim lines).
  2. Reviews: review#1 PASS 6859128505 on c35a911b (s1: land after fix(metadata-protocol)!: two packages' declared overrides of one flow or action name each serve in their own package's slot, in any registration order (ADR-0150 D4) #33126 and keep the census in step with its scope; n1 wording); delta PASS 6863098475 on this head, no findings, s1 and n1 closed.
  3. Order: fix(metadata-protocol)!: two packages' declared overrides of one flow or action name each serve in their own package's slot, in any registration order (ADR-0150 D4) #33126 (metadata-protocol: getMetaItemsForExecution files one pair package's bound flow override under another package's slot, by registration order (ADR-0150 D4, red line 2) #32613) merged first (840372560a), as s1 asked. This head merged main once after it (ae11537e, no conflict); the census now judges every shipper for flow and action only, and the six sealed types keep the first-registered shipper, matching declinesStoredRow, until metadata-protocol: a second holder's declared .custom override is still judged against the first shipper only, for the sealed served-override types and the overlay list filter (ADR-0150 D4 follow-ups of #32613) #33131. A mapping CONTROL (both orders) pins that.
  4. Verification on this head (report on the card): ablation leg A (type restriction removed) red on exactly the two mapping CONTROLs, leg B (fix reverted) 12 of 45 red, restore 45/45; metadata-protocol and objectql suites green, both typechecks 0; dispatch-gates 69/69 with dual-build-cjs-loads measured, 0 NOT-MEASURED; eslint 0. The full dogfood battery was green on round 1 (c35a911b); this round moves only the census in metadata-protocol plus main's merge, and the merge queue runs the full suite on the merged tree.
  5. Gates on this head: 37 check-runs success, 18 skipped, none failed or pending. K = 2, mergeable_state: clean.
  6. Carried: metadata-protocol: the boot census reports a second holder's served declared override as unserved and prescribes deleting the stored row #32859's census half is the third row this PR moves; its remainder is judged after landing. The six sealed types → metadata-protocol: a second holder's declared .custom override is still judged against the first shipper only, for the sealed served-override types and the overlay list filter (ADR-0150 D4 follow-ups of #32613) #33131.
  7. Landing: pr_ready, then auto-merge through the merge queue. The seat follows the PR to MERGED and verifies that metadata-protocol: removing the second holder's .custom override of a shared name judges the pair row against a bare-name owner lookup, so it may depend on registration order (ADR-0150 D4) #32552 closes.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 13, 2026 19:43
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 13, 2026
Merged via the queue into main with commit 128b657 Nov 13, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-h61law branch November 13, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants